<!DOCTYPE html>
<head>
   <title>Documentation</title>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" / >
   <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
   <script src="//code.jquery.com/jquery-2.1.0.min.js" 
      type="text/javascript"></script>
   <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js" 
      type="text/javascript"></script>
   <script src="//cdnjs.cloudflare.com/ajax/libs/ace/1.1.3/ace.js" 
      type="text/javascript" charset="utf-8"></script>

   <style>
      code { color: #000000; }
      pre { 
         padding: 0px 10px 0px 10px; 
      }
      .ItemRow { margin: 5px 0px 5px 0px; }
      .ItemCol { padding: 0; }
      .ItemPanel { margin: 0px; }
      .ItemBody { padding: 5px; }
      .ItemMethod { margin: 0px; }
      .ItemTable { table-layout: fixed; }
      .ItemTableType { width: 80px; }
      .ItemTableFormat { width: 80px; }
      .TryURIParameter { width: 75px; }
      .string { color: blue; }
      .number { color: blue; }
      .boolean { color: blue; }
      .null { color: blue; }
      .key { color: black; }
      .ItemTableLength {
         width: 50px;
         text-align: right;
      }
      .JSONTextArea {
         color: #000000;
         font-size: 1em;
      }
   </style>


   <script type="text/javascript">
   
$(document).ready(function () {
   var allMethods = ["GET", "POST", "PUT", "DELETE"];

   var methodPanelClass = {
      "GET": "success",
      "POST": "info",
      "PUT": "warning",
      "DELETE": "danger"
   }

   var methodButtonClass = {
      "GET": "success",
      "POST": "primary",
      "PUT": "warning",
      "DELETE": "danger"
   }

   $.getJSON("autodoc", function(data, status) {
      showAlert(data["config"]);
      showReferenceDocs(data["literate"]);
      showInteractiveDocs(data["routes"]);
   });

   function showAlert(config) 
   {         
      if(config != null)
      {
         if(config.alert != null && config.alert.contents != null) 
         {
            var container = $('<div>')
               .css({"width":"650px", "margin":"15px auto", "text-align": "center"})
               .appendTo($("body"));
            var alert = $("<div>").addClass("alert alert-dismissable")
               .html(config.alert.contents)
               .prepend($('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">').html("&times;"))
               .addClass("alert-"+(config.alert.type == null ? "info" : config.alert.type))
               .appendTo(container);
         }
      }
   }

   function onHeadingClick(event)
   {
      var $heading = $(event.currentTarget);
      var $group = $('.ItemGroup', $heading);
      var $item = $group.parents('.ItemPanel');

      var state = $group.data('state');


      if (state != 'closed') {
         var selector = "." + state;
         $(selector, $group).toggleClass('active', false).html(state);
         closePanel(state, $item);
         $group.data('state', 'closed');
      } else {
         $('.btn', $group).not(".btn-default")[0].click();
      }

      event.stopPropagation();
   }

   function onMethodButtonClick(event)
   {
      var $button = $(event.currentTarget);
      var $group = $button.parents('.ItemGroup');
      var $item = $group.parents('.ItemPanel');

      $button.toggleClass('active');
      var selected = $button.hasClass('active');

      // var type = $button.text();
      var type = getButtonType($button);
      var state = $group.data('state');

      $button.html(type);

      if (state != 'closed') {
         // close the open panel first
         var selector = "." + state;
         $(selector, $group).toggleClass('active', false).html(state);
         closePanel(state, $item);
         state = 'closed';
      }

      if (selected) {
         // show selected panel
         openPanel(type, $item);
         state = type;
      }

      $group.data('state', state);

      event.stopPropagation();
   }

   // NOTE Try It buttom bacomes submit button when interactive part is expanded.
   function onTryButtonClick(event)
   {
      var $tryItButton = $(event.currentTarget);
      var $tryURI = $tryItButton.siblings('.TryURI');
      var $well = $tryItButton.parents('.InteractionWell');
      var $panel = $tryItButton.parents('.ItemMethod');
      var $jsonTextArea = $('.JSONTextArea', $well);

      var $jsonSyntaxError = $('.JSONSyntaxError');
      $jsonSyntaxError.remove();

      var id = $panel.data('id');
      var inputData = {};

      // clean up
      var $exchange = $('.Exchange', $well);
      $exchange.remove();
      // send the request

      var method = $tryURI.data("method")
      var uri = $tryURI.data("uri");
      var error = false;
      $('.TryURIParameter', $tryURI).each(function() {
         if(error) return;
         if($(this).val().trim() == "") {
            error = true;
            return; // abort!
         }
         uri = uri.replace($(this).data("replacetext"), $(this).val());
      });

      if(error) {
            $('<span>')
                .addClass('JSONSyntaxError')
                .css('color', '#CC0000')
                .text("Missing a URL parameter")
                .insertAfter($tryItButton);
            return; // abort!
      }

      if (method === 'POST' || method === 'PUT') {
         try {
            var editor = ace.edit($jsonTextArea[0]);
            inputData = editor.getValue(); 
            $.parseJSON(inputData); //try a parse in case it throws
         }
         catch (e) {
            var $error = $('<span>')
                         .addClass('JSONSyntaxError')
                         .css('color', '#CC0000')
                         .text("JSON Syntax Error " + e.message)
                         .insertAfter($tryItButton);
            return; // abort!
         }

      }   
      submitIt(id, method, uri, inputData);
   }

   function replacement(match)
   {
      cls = 'number';
      if (/^"/.test(match)) {
         if (/:$/.test(match)) {
            cls = 'key';
         } else {
            cls = 'string';
         }
      } else if (/true|false/.test(match)) {
         cls = 'boolean';
      } else if (/null/.test(match)) {
         cls = 'null';
      }

      return '<span class="' + cls + '">' + match + '</span>';
   }


   // Return syntax highlighted HTML given plain JSON string.
   function syntaxHighlight(json)
   {
      json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
      return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, replacement);
   }


   function getButtonType($button)
   {
      var type = null;
      if ($button.hasClass('GET')) type = 'GET';
      if ($button.hasClass('POST')) type = 'POST';
      if ($button.hasClass('PUT')) type = 'PUT';
      if ($button.hasClass('DELETE')) type = 'DELETE';
      return type;
   }

   function openPanel(type, $container)
   {
      $('.ItemBody', $container).css('display', 'block');

      var panel = '.' + 'panel' + '.' + type;
      $(panel, $container).css('display', 'block');
   }

   function closePanel(type, $container)
   {
      $('.ItemBody', $container).css('display', 'none');

      var panel = '.' + 'panel' + '.' + type;
      $(panel, $container).css('display', 'none');
   }



   function submitIt(id, method, uri, inputData)
   {  
      var renderRequest = function(id, jqXHR)
      {
         var $panel = null;
         $('.ItemMethod').each(function(index) {
            if (id === $(this).data('id')) {
               $panel = $(this);
            }
         });

         var $well = $('.InteractionWell', $panel);
         var $interaction = $('<div>')
                            .addClass('panel panel-default pull-right Exchange')
                            .appendTo($well);   
         if(method == "POST" || method == "PUT") {
            $interaction.css("width", "49%");
         }
         else {
            $interaction.css("width", "100%");
         }

         var $exchange = $('<div>')
                         .addClass('panel-body')
                         .appendTo($interaction);

         var requestHTML = method + ' ' + uri + '<br>';
         //var cookieHTML = 'Cookie : ' + JSON.stringify($.cookie()) + '<br>';
         var contentInHTML = '';

         if(method=="POST" || method=="PUT") {
            contentInHTML = 'Content-Type : application/json<br>';
         }

         var $request = $('<pre>')
                    .html(requestHTML + contentInHTML)
                    .appendTo($exchange);

         var messageHTML = jqXHR.status + ' ' + jqXHR.statusText + '<br>';
         var contentOutHTML = 'Content-Type : ' + jqXHR.getResponseHeader('Content-Type') + '<br>';
         var contentLengthHTML = 'Content-Length : ' + jqXHR.getResponseHeader('Content-Length') + '<br>';
         var responseTextHTML = jqXHR.responseText;
         if(jqXHR.getResponseHeader('Content-Type') == "application/json") {
            responseTextHTML = syntaxHighlight( JSON.stringify(
                  JSON.parse(responseTextHTML), undefined, 3
               ) );
         }
         var $response = $('<pre>').css({"max-height":"400px","overflow":"scroll"})
                     .html(messageHTML + contentOutHTML + contentLengthHTML + '<br>' + responseTextHTML)

         if(jqXHR.status >= 400) {
            $response.css("border-color", "#CC6060");
         }
         $response.appendTo($exchange);
      }

      var parameters = {
         url : uri,
         type : method,
         success : function(response, status, jqXHR){
            renderRequest(id, jqXHR);
         },
         error : function(jqXHR){
            renderRequest(id, jqXHR);
         }
      }

      if(method=="POST" || method=="PUT") {
         parameters.data = inputData;
         parameters.contentType = "application/json";
      }

      $.ajax(parameters);
   }

   function processData(data)
   {
      var endpoints = []; 
      var lastURI = null; 

      for (var i = 0, all = data.length; i < all; i++) {
         var seperated = data[i][0].split(" ")
         var method = seperated[0].trim();
         var uri = seperated[1].trim();


         var details = data[i][1];
         var docstring = (!details) && (!details.docstring) ? '' : details.docstring;
         var role = (!details) && (!details.required_role) ? '' : details.required_role;

         var input = (!details) ? null : details.in; 
         var output = (!details) ? null : details.out; 

         if (uri === lastURI) {
            // augment endpoint
            var last = endpoints.length - 1;
            endpoints[last].methods.push(method);
            endpoints[last].docs.push(docstring);
            endpoints[last].roles.push(role);
            endpoints[last].inputs.push(input);
            endpoints[last].outputs.push(output);
            endpoints[last].ids.push(i);
         } else {
            // new endpoint
            var item =
            {
               uri : uri,
               methods : [ method ],
               docs : [ docstring ],
               roles : [ role ],
               inputs : [ input ],
               outputs : [ output ],
               ids : [ i ]
            }
            endpoints.push(item);
         }

         lastURI = uri;
      }

      return endpoints;
   }   


   function showInteractiveDocs(routes)
   {

      var $body = $('body');

      var $container = $('<div>').addClass('container');
      $container.appendTo($body);

      var $pageHeader = $('<div>')
                    .addClass('page-header')
                    .appendTo($container);
      var $h2 = $('<h2>')
            .text('Interactive Documentation')
            .appendTo($pageHeader);

      var listing = processData(routes);
      listing.sort(function(a,b){ return a.uri.localeCompare(b.uri); });
      for (var i = 0, all = listing.length; i < all; i++) {

         var endpoint = listing[i]; 
         var uri = endpoint.uri;
         var methods = endpoint.methods;
         var docs = endpoint.docs;
         var ids = endpoint.ids;

         var $row = $('<div>')
                    .addClass('row ItemRow')
                    .appendTo($container);

         var $col = $('<div>')
                    .addClass('col-xs-12 ItemCol')
                    .appendTo($row);

         var $panel = $('<div>')
                      .addClass('panel panel-default ItemPanel')
                      .attr('id', 'item' + i)
                      .appendTo($col);

         var $heading = $('<div>')
                        .addClass('panel-heading ItemHeading')
                        .appendTo($panel);

         $heading.click(onHeadingClick);               

         var $body = $('<div>')
                     .addClass('panel-body ItemBody')
                     .css('display', 'none')
                     .appendTo($panel);

         // button group           
         var $group = $('<div>')
                      .addClass('btn-group btn-group-xs ItemGroup')
                      .data('state', 'closed')
                      .appendTo($heading);

         // URI
         var $uri = $('<strong>')
                    .text(' ' + uri)
                    .appendTo($heading);

         var method, _i, _len;

         for (_i = 0, _len = allMethods.length; _i < _len; _i++) {
            method = allMethods[_i];
            var enabled = (methods.indexOf(method) != -1);
            $group.append(createMethodButton(method, enabled));  
            if (enabled) {
               $body.append(createPanel(method, endpoint));
            }
         }
      }

      $("<div class='text-center' style='color: #aaa'>").text("© Copyright 2010-2014 mldb.ai inc. All Rights Reserved").appendTo($container);
   }


   function showReferenceDocs(literate)
   {
      if(literate.length == 0) return;

      var $body = $('body');

      var $container = $('<div>').addClass('container');
      $container.appendTo($body);

      var $pageHeader = $('<div>')
                    .addClass('page-header')
                    .appendTo($container);
      var $h2 = $('<h2>')
            .text('Reference Documentation')
            .appendTo($pageHeader);

      var ul = $("<ul>");
      for (var i = 0; i < literate.length; i++) {
         ul.append(
            $("<li>").append(
               $("<a>")
                  .attr("href", "literate/"+literate[i].filename)
                  .text( literate[i].name) 
            )
         );
      }

      $container.append(ul);
   }
 
   function createMethodButton(method, enabled)
   {
      var style = 'btn-default';

      if (enabled) {
         style = "btn-" + methodButtonClass[method];
      }

      var classes = 'btn ' + style + ' ' + method;

      var $button = $('<div>')
                    .addClass(classes)
                    .attr('type', 'button')
                    .html(method);

      if (!enabled) {
         $button.attr('disabled', 'disabled');     
         $button.css('cursor', 'default');     
      }   

      $button.click(onMethodButtonClick);     

      return $button;
   }


   // Single endpoint panel for a given method method (GET, POST, PUT, DELETE).
   function createPanel(method, endpoint)
   {

      var index = endpoint.methods.indexOf(method);
      var description = endpoint.docs[index];
      var role = endpoint.roles[index];
      var input = endpoint.inputs[index];
      var output = endpoint.outputs[index];
      var id = endpoint.ids[index];

      var properties = output.properties;
      var required = output.required;

      // main panel container
      var $panel = $('<div>')
                   .addClass(method+ ' ItemMethod panel panel-' + methodPanelClass[method])
                   .css('display', 'none')
                   .data('id', id);

      // heading with description (and role inside)
      var $heading = $('<div>')
                     .addClass('panel-heading')
                     // .text(description + ' [ role : ' + role + ' ]')
                     .appendTo($panel);

      var $description = $('<div>').addClass('clearfix').appendTo($heading);
      if(description == null) description = "";
      var $doc = $('<div>').addClass('pull-left').text(' ' + description).appendTo($description);
      var $label = $('<span>')
         .addClass('label label-' +  methodButtonClass[method])
         .text(method).prependTo($doc);
      var $role = $('<div>').addClass('pull-right').text('[ role : ' + role + ' ]').appendTo($description);

      var $body = $('<div>')
                  .addClass('panel-body')
                  .appendTo($panel);

      // input reference
      if(!$.isEmptyObject(input.properties) || !$.isEmptyObject(input.items))
      {
         $('<span>').addClass('label label-default').text('INPUT').appendTo($body);
         $body.append(createTable(input)); 
      }

      // output reference
      if(!$.isEmptyObject(output.properties) || !$.isEmptyObject(output.items))
      {      
         $('<span>').addClass('label label-default').text('OUTPUT').appendTo($body);
         $body.append(createTable(output));            
      }

      // interactive section
      var $well = $('<div>')
                  .addClass('well well-sm clearfix InteractionWell')
                  .data('mode', 'collapsed')
                  .appendTo($body);


      var $tryURI = $('<div>')
                    .addClass('pull-left TryURI')
                    .data('uri', endpoint.uri)
                    .data("method", method)
                    .appendTo($well);
      var $code = $('<span>')
                    .css({"font-family": "Courier"})
                    .appendTo($tryURI);              

      var segments = endpoint.uri.split("/");
      var accumulator= method + " ";
      for(var i=0; i<segments.length; i++) {
         if(segments[i] =="") continue;
         if(segments[i][0] != "<") { 
            accumulator += "/"+segments[i];
         }
         else {
            $('<span>')
               .text(accumulator+"/")
               .appendTo($code);
            accumulator = "";
            $("<input>")
               .attr('type', 'text')
               .addClass("TryURIParameter")
               .attr('placeholder', segments[i])
               .data("replacetext", segments[i])
               .appendTo($code);
         }
      }
      if(accumulator != "") {
         $('<span>').text(accumulator).appendTo($code);
      }

      var $tryButton = $('<button>')
                       .addClass('btn btn-default btn-xs pull-left TryItButton')
                       .attr({'type' : 'button', 'tabindex' : 0})
                       .css({"margin": "auto 5px"})
                       .text('Send ' + method + ' request')
                       .appendTo($well);

      $tryButton.click(onTryButtonClick);

      $('<br><br>').appendTo($well);   

      if(method == "POST" || method == "PUT")
      {
         var starterJSON = createStarterJSON(input, 0); 
         var $jsonTextArea = $('<div>')
                          .addClass('form-control pull-left JSONTextArea')
                          .css({'height': "400px", "width": "49%"});
         $jsonTextArea.appendTo($well);

         var editor = ace.edit($jsonTextArea[0]);
         editor.setValue(starterJSON);
         editor.clearSelection();
         var session = editor.getSession();
         session.setMode("ace/mode/json");
         session.setTabSize(2);
      }

      return $panel;
   }

   function createStarterJSON(schema, level)
   {
      var result = "";
      switch (schema.type) {
         case "integer":
            result += "1";
            break;
         case "number":
            result += "1";
            break;
         case "boolean":
            result += "true";
            break;
         case "string":
            if(schema.enum) {
               result += '"' +  schema.enum[0] + '"';
            }
            else if(schema.patternName == "ISO 8601 date-time") {
               result += '"' + (new Date()).toISOString() + '"';
            }
            else {
               result += '"string"';
            }
            
            break;
         case "array":
            result += "[ " + createStarterJSON(schema.items, level) + " ]";
            break;
         case "object":
         default:
            result += "{\n";
            for (var p in schema.properties) {
               if (schema.properties.hasOwnProperty(p)) {
                  for (var i=0; i<=level; i++) result+="  ";
                  result += '"'+p+'": ';
                  result += createStarterJSON(schema.properties[p], level+1);
                  result += ",\n";
               }
            }
            result = result.replace(/,\n$/,"\n");
            for (var i=0; i<level; i++) result+="  ";
            result += "}";
            break;
      }
      return result;
   }
   
   function createTable(io)
   {
      var $table = $('<table>')
                   .addClass('table table-condensed ItemTable');

      var $trhead = $('<tr>').appendTo($table);

      $('<th>').text('Key').css("width", "250px").appendTo($trhead);
      $('<th>').text('Type').css("width", "200px").appendTo($trhead);
      $('<th>').text('Description').appendTo($trhead);
      $('<th>').text('Constraints').appendTo($trhead);

      addSchemaToTable($table, io, 0, "[root]", false, false);
      return $table;
   }

   function addSingleSchemaRow(table, level, type, name, details, optional, desc) {
      if(optional) { name += " (optional)"; }
      if(desc == null) { desc = ""; }
      table.append( 
         $('<tr>').append(
            $('<td>').text(name).css("padding-left", (5+level*20) + "px"),
            $('<td>').text(type),
            $('<td>').text(desc),
            $('<td>').text(details.join("; "))
         )
      );
   }

   function addSchemaToTable(table, schema, level, name, isArray, optional)
   {
      var prefix = isArray? "array of " : "";
      var details = [];
      switch (schema.type) {
         case "integer":
         case "number":
         case "boolean":
         case "string":
            if(schema.enum) {
               details.push("one of: " + schema.enum.join(", "));
            }
            else if(schema.patternName) {
               details.push("pattern: " + schema.patternName);
            }
            else if(schema.maxLength) {
               details.push("max length: " + schema.maxLength);
            }
            addSingleSchemaRow(table, level, prefix+schema.type, name, 
               details, optional, schema.description)
            break;
         case "array":
            addSchemaToTable(table, schema.items, level, name, true, optional)
            break;
         case "object":
         default:
            addSingleSchemaRow(table, level, prefix+"object", name, 
               details, optional, schema.description)
            for (var p in schema.properties) {
               if (schema.properties.hasOwnProperty(p)) {
                  var subSchema = schema.properties[p];
                  var subOptional = schema.required.indexOf(p) < 0;
                  addSchemaToTable(table, subSchema, level+1, p, false, subOptional)
               }
            }
            break;
      }
   }
});

   </script>
</head>
<body>
</body>
</html>
